package qj.pooling;

import qj.concurrent.ServantTask;
import qj.concurrent.Workable;

import java.util.*;

/**
 * This abstract pool provide a simple implementation of pooling technique.
 * When acquire method called, an available object is returned, that object
 * will be marked locked by the pool, and can not be acquired by other processes
 * until release method is called on that object again.
 *
 * If the pool has max size, acquire will return null.
 *
 * The pool need a manager, or else it will throw NullPointerException
 *
 * Be careful of acquire no release error.
 * Created by QuanLA
 * Date: Mar 14, 2006
 * Time: 8:56:50 AM
 */
public class SimpleObjectPool implements ObjectPool {
    private PoolManagable poolManager;

    private List objectsLocked  = new ArrayList();
    private List objectsReady   = new ArrayList();

    // Keep track of the size of bath objects
    private int readySize   = 0;
    // Keep track of the size of busy objects
    private int lockSize    = 0;

    private Workable poolCleaner = null;

    private int scheduledCleaningTime = 300000;

    private int restrictSize = 0;

    private final CleaningTask cleaningTask = new CleaningTask();

    protected SimpleObjectPool() {
    }

    protected SimpleObjectPool(PoolManagable poolManager) {
        this.poolManager = poolManager;
    }

    protected SimpleObjectPool(Workable poolCleaner) {
        this.poolCleaner = poolCleaner;
    }

    /**
     * Get an object from the pool
     *
     * If there are no available object, then create a new one buy calling createNew(), method.
     * @return pooled object
     * @throws IllegalArgumentException: when the pool manager can not prepare a
     *          brand new created object
     */
    public synchronized Object acquire() {
        if (restrictSize>0 && readySize==0 && size()>=restrictSize)
            return null;

        // Try acquiring and prepare the object from the ready place.
        while ( readySize > 0) {
            // We still have bath objects
            Object obj = objectsReady.remove(--readySize);

            if (!poolManager.prepare(obj)) {
                poolManager.destroy(obj);
                continue;
            }

            // The obj is prepared successfully
            objectsLocked.add(obj);
            lockSize++;

            return obj;
        }

        // Have to create new;
        Object obj = poolManager.createNew();

        if (!poolManager.prepare(obj)) {
            poolManager.destroy(obj);
            throw new IllegalArgumentException("Cannot prepare a brand-new object, object created new by " + poolManager.getClass().getName());
        }

        objectsLocked.add(obj);
        lockSize++;

        poolManager.prepare(obj);
        return obj;
    }

//    private int countRelease = 0;

    /**
     * Has to be called after using the object.
     * If not, the object will never be release and system meet out of memory error.
     * @param obj
     */
    public synchronized void release(Object obj) {

        if (obj==null)
            return;

        objectsLocked.remove(obj);
        lockSize--;

        // If the coming back obj can bath, add it to ready place, else destroy it.
        if (poolManager.bath(obj)) {
            objectsReady.add(obj);
            readySize++;
        } else
            poolManager.destroy(obj);


        // Schedule clean in next few minute
        if (poolCleaner!=null) {
            poolCleaner.schedule(cleaningTask, scheduledCleaningTime);
        }

        // Notify any object waiting on this pool to get object.
        notify();
    }

    /**
     * Get the current size of the pool
     * @return size
     */
    public int size() {
        return lockSize + readySize;
    }

    /**
     * This will bath up all currently not used objects.
     */
    public synchronized void destroyAllFreeObjects() {
        Object temp;
        for (;readySize > 0;readySize--) {
            temp = objectsReady.remove(readySize - 1);
            poolManager.destroy(temp);
        }
    }

    public void setPoolCleaner(Workable poolCleaner) {
        this.poolCleaner = poolCleaner;
    }

    public void setRestrictSize(int restrictSize) {
        this.restrictSize = restrictSize;
    }

    public void setPoolManager(PoolManagable poolManager) {
        this.poolManager = poolManager;
    }

    /**
     * scheduledCleaningTime is the delay before the pool get the worker to clean it self.
     * @param scheduledCleaningTime
     */
    public void setScheduledCleaningTime(int scheduledCleaningTime) {
        this.scheduledCleaningTime = scheduledCleaningTime;
    }

    /**
     * Cleaning Task
     */
    private class CleaningTask extends ServantTask {

        public void run() {
            destroyAllFreeObjects();
        }
    }

    public synchronized Object[] getAllInUseObjects() {
        return objectsLocked.toArray();
    }
}
